import { VRC6Mixer } from "../Audio/VRC6/VRC6Mixer";
import { Bus } from "../Bus";
import { MirrorType } from "../PPU/PPU";
import { Tile } from "../PPU/PPUBlock";
import { IMapper, MapperInitOption } from "./IMapper";

const PrgMaxAnd = 0x1FFF;
const PreScaler = 341;

/**
 * https://www.nesdev.org/wiki/VRC6
 */
export class Mapper24 implements IMapper {

	bus: Bus;
	prgSize: number = PrgMaxAnd + 1;
	chrSize: number = 0x400;

	private ppuBankMode = 0;

	private irq = {
		reloadValue: 0,
		counter: 0,
		enable: false,
		acknowledgeFlag: false,
		acknowledge: false,
		cycleMode: false,
		prescaler: PreScaler,
	}

	private mixer: VRC6Mixer;

	constructor(bus: Bus) {
		this.bus = bus;
		this.mixer = new VRC6Mixer(bus);
	}

	Initialization(option: MapperInitOption): void {
		this.bus.cartridge.prgIndex = [0, 0, 0, option.maxPrg];
		this.bus.cartridge.chrIndex = [0, 0, 0, 0, 0, 0, 0, 0];

		this.bus.apu.SetExternalSound(this.mixer);
	}

	ReadPRG(address: number): number {
		const index = this.bus.cartridge.prgIndex[(address - 0x8000) >> 13];
		address &= PrgMaxAnd;
		return this.bus.cartridge.prgBanks[index][address];
	}

	WritePRG(address: number, value: number): void {
		switch (address) {
			case 0x8000:
				value = value << 1;
				this.bus.cartridge.prgIndex[0] = value;
				this.bus.cartridge.prgIndex[1] = value + 1;
				break;
			case 0x9000:
			case 0x9001:
			case 0x9002:
			case 0xA000:
			case 0xA001:
			case 0xA002:
			case 0xB000:
			case 0xB001:
			case 0xB002:
				this.mixer.WriteIO(address, value);
				break;
			case 0xB003:
				this.ppuBankMode = value & 3;
				this.SetMirrorType(value & 0xC);
				break;

			case 0xC000:
				this.bus.cartridge.prgIndex[2] = value;
				break;
			case 0xD000:
			case 0xD001:
			case 0xD002:
			case 0xD003:
				this.bus.cartridge.chrIndex[address - 0xD000] = value;
				break;
			case 0xE000:
			case 0xE001:
			case 0xE002:
			case 0xE003:
				this.bus.cartridge.chrIndex[address - 0xDFFC] = value;
				break;
			case 0xF000:
				this.irq.reloadValue = value;
				break;
			case 0xF001:
				this.irq.cycleMode = (value & 4) === 0;
				this.irq.enable = (value & 2) !== 0;
				this.irq.acknowledgeFlag = (value & 1) !== 0;
				this.irq.prescaler = PreScaler;
				if (this.irq.enable)
					this.irq.counter = this.irq.reloadValue;
				break;
			case 0xF002:
				// IRQ Acknowledge
				// Any write to this register will acknowledge the pending IRQ. 
				// In addition, the 'A' control bit moves to the 'E' control bit, enabling or disabling IRQs.
				// Writes to this register do not affect the current state of the IRQ counter or prescaler.
				this.irq.enable = this.irq.acknowledgeFlag;
				this.irq.acknowledgeFlag = false;
				this.bus.cpu.SetInterruptValue("IRQ", false);
				break;
		}
	}

	ReadCHR(address: number): number {
		const tile = this.GetCHRTile(address >> 4);
		return tile.data[address & 0xF];
	}

	GetCHRTile(tileIndex: number): Tile {
		const index = this.bus.cartridge.chrIndex[tileIndex >> 6];
		tileIndex &= 0x3F;
		return this.bus.cartridge.chrBanks[index][tileIndex];
	}

	WriteCHR(address: number, value: number): void { }

	/**
	 * When in scanline mode ('M' bit clear), a prescaler divides the passing CPU cycles by 114, 114, then 113 (and repeats that order). 
	 * This approximates 113+2/3 CPU cycles, which is one NTSC scanline.
	 * When the prescaler is reset (by writing to the IRQ Control register with 'E' set), 
	 * the sequence is reset and it will be 114 CPU cycles before the IRQ counter will be clocked.
	 * 当处于扫描线模式（M’bit clear）时，预分频器将经过的CPU周期除以114、114，然后除以113（并重复该顺序）。
	 * 这近似于113+2/3个CPU周期，即一条NTSC扫描线。
	 * 当预分频器复位时（通过写入设置了“E”的IRQ控制寄存器），序列复位，并且在IRQ计数器计时之前将是114个CPU周期。
	 * 
	 * A simple way to emulate this behavior is to have the prescaler start at 341 and subtract 3 every CPU cycle. 
	 * When it drops to or below 0, increment it by 341 and clock the IRQ counter.
	 * 模拟这种行为的一种简单方法是使预分频器从341开始，并在每个CPU周期减去3。当它降到或低于0时，将其递增341，并为IRQ计数器计时。
	 * 
	 * When in cycle mode ('M' bit set), the prescaler is effectively bypassed, and the IRQ counter gets clocked every CPU cycle.
	 * When the IRQ counter is clocked:
	 * If IRQ counter is $FF, reload IRQ counter with latch value 'L', trip IRQ
	 * otherwise, increment IRQ counter by 1
	 * If IRQs are disabled ('E' bit clear), neither the prescaler nor IRQ counter gets clocked.
	 * 当处于循环模式（M’位设置）时，预分频器被有效绕过，并且IRQ计数器在每个CPU循环中被计时。
	 * 
	 * 当IRQ计数器计时时：
	 * 如果IRQ计数器为$FF，则用锁存值“L”重新加载IRQ计数器，使IRQ跳闸
	 * 否则，将IRQ计数器递增1
	 * 如果IRQ被禁用（“E”位清除），则预分频器和IRQ计数器都不会被计时。
	 */
	CPUClockEvent(): void {
		if (!this.irq.enable)
			return;

		this.irq.prescaler -= 3;
		if (this.irq.cycleMode || (this.irq.prescaler <= 0 && !this.irq.cycleMode)) {
			if (this.irq.counter === 0xFF) {
				this.irq.counter = this.irq.reloadValue;
				this.bus.cpu.SetInterruptValue("IRQ", true);
			} else {
				this.irq.counter++;
			}
			this.irq.prescaler += 341;
		}

		// if (this.irq.cycleMode && this.irq.prescaler > 0)
		// 	return;
		// else if (this.irq.prescaler <= 0)
		// 	this.irq.prescaler += 341;


		// if (!this.irq.cycleMode || this.irq.counter++ === 0xFF) {
		// 	this.irq.counter = this.irq.reloadValue;
		// 	this.bus.cpu.SetInterrupt("IRQ");
		// 	// console.log(this.bus.ppu.scanLine);
		// }
	}

	private SetMirrorType(type: number) {
		switch (type) {
			case 0:
				this.bus.ppu.SetMirrorType(MirrorType.Vertical);
				break;
			case 4:
				this.bus.ppu.SetMirrorType(MirrorType.Horizontal);
				break;
			case 8:
				this.bus.ppu.SetMirrorType(MirrorType.NT0);
				break;
			case 0xC:
				this.bus.ppu.SetMirrorType(MirrorType.NT2);
				break;
		}
	}

}